package algo.implementation;
/**
 * Check this page for RAMDirectory based on a DiskIndex:
 * 	http://stackoverflow.com/questions/673887/using-ramdirectory
 */
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.regex.Pattern;

import memetic.operators.Diversification;
import memetic.operators.LocalSearch;
import memetic.operators.NeighborhoodGeneration;
import memetic.operators.PopulationInitilization;

import funtions.FitnessFunction;


public class AlgorithmExecution {
	
	private static Charset charset = Charset.forName("ISO8859_1");
	private static CharsetEncoder encoder = charset.newEncoder();
	private static CharsetDecoder decoder = charset.newDecoder();
	private Pattern psentInstancesSplit=Pattern.compile("\n");
	private Pattern psentInstFieldSplit=Pattern.compile("\t");
	/*
	 * 
	 */
	private FitnessFunction fitnessFunct;
	private PopulationInitilization populationInit;
	private NeighborhoodGeneration neighborhoodGen;
	private LocalSearch localSearch;
	private Diversification diversification;
	/*
	 * Parameters for the Fitness Function
	 */
	private String [] sentInfoSet;
	private String [] sentLengthSet;
	
	public void init(String sentTerm_Location,String sentLengthLocation){
		try {
			readFiles(sentTerm_Location,sentLengthLocation);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.err.println("Error reading the files needed for the initialization");
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println("Error reading the files needed for the initialization");
		}
	}
	/**
	 * This method receives a String returns the ByteBuffer that represents that String.
	 * @param String String to be transformed.
	 * @return ByteByffer ByteBuffer representation of the String. 
	 */
	public ByteBuffer stringToBBuffer(String msg){
		try{
		    return encoder.encode(CharBuffer.wrap(msg));
		  }catch(Exception e){e.printStackTrace();}
		  return null;
	}
	/**
	 * This method receives a ByteBuffer and returns the String that it represents.
	 * @param ByteBuffer ByteBuffer to be transformed.
	 * @return String String representation of the ByteBuffer. 
	 */
	public String BBuffertoString(ByteBuffer buffer){
		String data = "";
		  try{
		    int old_position = buffer.position();
		    data = decoder.decode(buffer).toString();
		    // reset buffer's position to its original so it is not altered:
		    buffer.position(old_position);  
		  }catch (Exception e){
		    e.printStackTrace();
		    return "";
		  }
		  return data;
	}
	/**
	 * This gets the current date.
	 * @param String Date format.
	 * @return String current Date. 
	 */
	public String now(String dateFormat) {
	    Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
	    return sdf.format(cal.getTime());

	  }
	/**
	 * 
	 */
	public File[] getSubFolderFromFolfer(String mainFolderPath){
		File folder=new File(mainFolderPath);
		File [] subFolders=folder.listFiles();
		return subFolders;
	}
	/**
	 * @throws IOException
	 */
	private String getValidInformationFromBuff(FileChannel f,int inBufCap,ByteBuffer inBuf, ByteBuffer auxBuf,float fsize) throws IOException{
		String validStringInfo="";
		//The variables below are utilitary variables which helps us to do the needed tasks
        char[] charArr;
        String s;
        long len = 0;
        int i=0;
        float i_filePosition=0,f_filePosition=0;
        while((len = f.read(inBuf)) != -1) {
    		inBuf.flip();
            //Profiling code lines
            s=BBuffertoString(inBuf);
            /**
             * Because the use of a fixed sized buffer it is possible to read incomplete instances, for example:
             * "First buffer read"
             * 		Instance_1  Field_1 Field_2 Part(Field_3)
             * "Second buffer read"
             * 		left_part(instance_1,Field_3)
             * 		Instance_2  Field_1 Field_2 Field_3
             * 
             * So the lines written below pretend to analyze the information in the buffer an only take the information where
             * the instances are complete. After the valid information was selected the position of the "filechannel" is reallocated
             * in order to start from the last valid end point.
             */
            //Transform the String information in the buffer, in a character array
            charArr=s.toCharArray();
            //Read the char array backwards
            for(i=charArr.length-1;i>-1;i--){
            	//Look for the last valid instance and used the other condition to avoid an infinite loop due to the reallocation
            	if(charArr[i]=='\n'&&(fsize-f.position())>=inBufCap){
            		//Extract the valid information
            		auxBuf=stringToBBuffer(s.substring(0, i));
            		//Calculate the new last valid position in the "filechannel"
            		f_filePosition=i_filePosition+auxBuf.limit();
            		//Reallocate
            		f.position((long)f_filePosition);
            		break;
            	}
            }
            //Clean the auxiliary variables
            charArr=null;
            //If it is the last chunk just write it, if it is analyzed as it was done previously you get a loop because the reallocation
            if((fsize-f.position())<inBufCap){
            	//Clean the auxiliary variables
            	auxBuf.clear();
            	//
            	validStringInfo=BBuffertoString(inBuf);
            }else{
            	//Clean the auxiliary variables
            	inBuf.clear();
            	//
            	validStringInfo=BBuffertoString(auxBuf);
            }
            //******
            inBuf.clear();
            //The line bellow stores the final valid position, where the file was read. 
            i_filePosition=f.position();
        }
		return validStringInfo;
	}
	/**
	 * Load into memory the files:
	 * 	- sentencesLength.txt
	 * 	- Sent-Term_freq
	 * @throws FileNotFoundException
	 * @throws IOException
	 * 
	 */
	private void readFiles(String sentTerm_Location,String sentLengthLocation) throws FileNotFoundException,IOException{
		int inBufCap=1*1024*1024;//size in bytes
		ByteBuffer inBuf = ByteBuffer.allocateDirect(inBufCap);
		//Auxiliary Buffer to store the valid information
        ByteBuffer auxBuf=ByteBuffer.allocateDirect(inBufCap);
        /*
         * "Sent-Term_freq.txt"
         */
        File sentTermFile=new File(sentTerm_Location);
        float fsize=0;
        fsize=sentTermFile.length();
		FileInputStream is=new FileInputStream(sentTermFile);
        FileChannel f=is.getChannel();
        //
        String auxString="";
        //
        auxString=getValidInformationFromBuff(f, inBufCap, inBuf, auxBuf, fsize);
        /*
         * Due to the last line change "\n" an empty sentence is introduced, generating and exception.
         */
        String[] auxSentTermArr=this.psentInstancesSplit.split(auxString);
        ArrayList<String> sentTermArrList=new ArrayList<String>();
        for(int x=0;x<auxSentTermArr.length;x++){
        	if(!auxSentTermArr[x].isEmpty()&&auxSentTermArr[x].trim().length()>0){
        		sentTermArrList.add(auxSentTermArr[x]);
        	}
        }
        String[] sentTermArray=new String[sentTermArrList.size()];
        sentTermArrList.toArray(sentTermArray);
        /*
         * End
         */
        this.sentInfoSet=sentTermArray;
        /*
         * "sentencesLength"
         */
        File sentLengthFile=new File(sentLengthLocation);
        fsize=0;
        fsize=sentLengthFile.length();
		is=new FileInputStream(sentLengthFile);
        f=is.getChannel();
        auxString=getValidInformationFromBuff(f, inBufCap, inBuf, auxBuf, fsize);
        /*
         * Due to the last line change "\n" an empty sentence is introduced, generating and exception.
         */
        String[] auxsentLengthArr=this.psentInstancesSplit.split(auxString);
        ArrayList<String> sentLengArrList=new ArrayList<String>();
        for(int x=0;x<auxsentLengthArr.length;x++){
        	if(!auxsentLengthArr[x].isEmpty()&&auxsentLengthArr[x].trim().length()>0){
        		sentLengArrList.add(auxsentLengthArr[x]);
        	}
        }
        String[] sentLengthArray=new String [sentLengArrList.size()];
        sentLengArrList.toArray(sentLengthArray);
        /*
         * End
         */
        this.sentLengthSet=sentLengthArray;
	}
	/**
	 * Transform a integers (primitive) array into a string separated by ","
	 */
	private String convertIntArray_String(int [] intArr){
		String returnString="";
		for(int w=0;w<intArr.length;w++){
			if(w==intArr.length-1){
				returnString+=intArr[w];
			}else{
				returnString+=intArr[w]+",";
			}
		}
		return returnString;
	}
	/**
	 * Given an individual gets the summary
	 * @throws IOException 
	 * 
	 */
	private void getFinalSummary(int[] optIndv,String resultDir,String validationFolder, String resfile) throws IOException{
		String summary="";
		for(int w=0;w<optIndv.length;w++){
			if(optIndv[w]==1){
				if(w==optIndv.length-1)
					summary+=this.psentInstFieldSplit.split(this.sentLengthSet[w])[2];
				else
					summary+=this.psentInstFieldSplit.split(this.sentLengthSet[w])[2]+" ";
			}
		}
		FileWriter fstream=new FileWriter(resultDir+"\\summary.txt",true);
		FileWriter fstream2=new FileWriter(validationFolder+"\\"+resfile+".txt",true);
		BufferedWriter out = new BufferedWriter(fstream);
		BufferedWriter out2 = new BufferedWriter(fstream2);
		out.write(summary);
		out2.write(summary);
		out.close();
		fstream.close();
		out2.close();
		fstream2.close();
	}
	/**
	 * 
	 * 
	 */
	private int[] refinement(int [] optIndv) {
		int summaryLength=this.fitnessFunct.calculateTotalLengthInd(optIndv);
		/*Max length defined for the summary
		 * 
		 */
		double iterFit=-1,maxFit=-1;
		int[] iterIndv=new int[optIndv.length];
		int[] optIterIndv=new int[optIndv.length];
		iterIndv=optIndv.clone();
		if(summaryLength>this.fitnessFunct.getMaxLength()){
			while(summaryLength>this.fitnessFunct.getMaxLength()){
				optIterIndv=optIndv.clone();
				iterFit=-1;
				maxFit=-1;
				for(int w=0;w<optIndv.length;w++){
					if (maxFit==-1&&optIndv[w]==1) {
						optIterIndv[w]=0;
						maxFit=this.fitnessFunct.calculateFitFunct(optIterIndv);
					}
					if (maxFit>-1&&optIndv[w]==1) {
						iterIndv[w]=0;
						iterFit=this.fitnessFunct.calculateFitFunct(iterIndv.clone());
						if (iterFit>maxFit) {
							optIterIndv=iterIndv.clone();
						}
						if(iterFit==maxFit&&
								this.fitnessFunct.calculateTotalLengthInd(iterIndv)
								>this.fitnessFunct.calculateTotalLengthInd(optIterIndv)){
							optIterIndv=iterIndv.clone();
						}
					}
				}
				optIndv=optIterIndv.clone();
				summaryLength=this.fitnessFunct.calculateTotalLengthInd(optIndv);
			}
		}
		System.out.println("Final maxLength: "+summaryLength);
		return optIndv;
	}
	/**
	 * Return index the best individual from the history
	 */
	public int getBestHistInd(ArrayList<Double> fitness_hist,ArrayList<Integer> len_hist){
		double max_history=fitness_hist.get(0).doubleValue();
		double max_history_len=len_hist.get(0).doubleValue();
		int max_history_pos=0;
		for (int w=1;w<fitness_hist.size();w++){
			if(w<fitness_hist.get(w).doubleValue()){
				max_history=fitness_hist.get(w).doubleValue();
				max_history_len=len_hist.get(w).doubleValue();
				max_history_pos=w;
			}
			if(w==fitness_hist.get(w).doubleValue()){
				if(max_history_len>len_hist.get(w).doubleValue()){
					max_history=fitness_hist.get(w).doubleValue();
					max_history_len=len_hist.get(w).doubleValue();
					max_history_pos=w;
				}
			}
		}
		return max_history_pos;
	}
	/**
	 * Algorithm Design
	 * @throws IOException 
	 */
	public void algorithmDesign_I(String resultsDir, int maxIterations, ByteBuffer outBuffer,FileChannel control, String validationFolder,String resfile) throws IOException{
		int iterationsCounter=0;
		this.fitnessFunct=new FitnessFunction(this.sentInfoSet, this.sentLengthSet);
		this.fitnessFunct.init();
		this.fitnessFunct.initCoverage();
		/*
		 * Initialization
		 */
		this.populationInit=new PopulationInitilization();
		int [] initInd=this.populationInit.initOneInd_random_PercPop(this.sentInfoSet.length, 1f);
		int [] iterIndividual=new int[initInd.length];
		int [] optIndividual=new int[initInd.length];
		
		//This attributes was created to store the history
		ArrayList<Double> fitness_history=new ArrayList<Double>();
		ArrayList<Integer> len_history=new ArrayList<Integer>();
		ArrayList<int[]> ind_history=new ArrayList<int[]>();
		//
		
		double fitnessValue=this.fitnessFunct.calculateFitFunct(initInd);
		double fitnessVariation=0,fit_I=0,fit_II=0;
		boolean considerDiver=false;
		int iterDiversification=0;
		//
		
		this.neighborhoodGen=new NeighborhoodGeneration();
		this.localSearch=new LocalSearch(this.fitnessFunct);
		this.diversification=new Diversification();
		String controlString="";
		this.diversification.init(this.fitnessFunct);
		//
		controlString+=iterationsCounter+"\t"+"INIT"+"\t"+convertIntArray_String(initInd)+"\t"+fitnessValue+"\t"+
		this.fitnessFunct.calculateTotalLengthInd(initInd)+"\n";
		outBuffer=stringToBBuffer(controlString);
		
		//Add to history
		fitness_history.add(new Double(fitnessValue));
		ind_history.add(initInd.clone());
		len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(initInd)));
		//
		
		while(iterationsCounter<=maxIterations){
			
			System.out.println("Iteration: "+iterationsCounter);
			/*
			 * Local search
			 */
			//this.neighborhoodGen.hammingDistance_one(initInd);
			//Hamming distance 1
			iterIndividual=this.localSearch.greedyLocalSearch(initInd, this.neighborhoodGen.hammingDistance_one(initInd));
			iterationsCounter=iterationsCounter+this.localSearch.neighborhood.length;
			fit_I=this.fitnessFunct.calculateFitFunct(initInd);
			fit_II=this.fitnessFunct.calculateFitFunct(iterIndividual);
			
			//Add to history
			fitness_history.add(new Double(fit_II));
			ind_history.add(iterIndividual.clone());
			len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(iterIndividual)));
			//
			
			fitnessVariation=Math.abs(fit_I-fit_II);
			controlString+=iterationsCounter+"\t"+"LS-H1"+"\t"+convertIntArray_String(iterIndividual)+"\t"+fit_II+
					"\t"+this.fitnessFunct.calculateTotalLengthInd(iterIndividual)+"\n";
			/*
			 * Diversification policy
			 */
			if(fitnessVariation<10){
				considerDiver=true;
			}
			if(considerDiver){
				iterDiversification=iterDiversification+this.localSearch.neighborhood.length;
			}else{
				iterDiversification=0;
			}
			if(considerDiver&&iterDiversification>(maxIterations*0.03)){
				initInd=this.diversification.mutation_percent(initInd.clone(), 3);
				iterationsCounter=iterationsCounter+1;
				
				fit_II=this.fitnessFunct.calculateFitFunct(initInd);
				
				//Add to history
				fitness_history.add(new Double(fit_II));
				ind_history.add(initInd.clone());
				len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(initInd)));
				//
				
				controlString+=iterationsCounter+"\t"+"M"+"\t"+convertIntArray_String(initInd)+"\t"
				+fit_II+"\t"+this.fitnessFunct.calculateTotalLengthInd(initInd)+"\n";
			}else{
				initInd=iterIndividual;
			}
			/*
			 * End diversification policy
			 */
			outBuffer=stringToBBuffer(controlString);
			if(outBuffer.limit()>(5*1024*1024)-(2*1024)){
				System.out.println("Writing control file...");
				control.write(outBuffer);
				outBuffer.flip();
				outBuffer.clear();
			}
		}
		control.write(outBuffer);
		//Refinement
		control.close();
		System.out.println("Length: "+this.fitnessFunct.calculateTotalLengthInd(iterIndividual));
		System.out.println("Refinando...");
		
		//get the best from history
		int bestPos=getBestHistInd(fitness_history, len_history);
		iterIndividual=ind_history.get(bestPos).clone();
		//
		
		optIndividual=refinement(iterIndividual.clone());
		getFinalSummary(optIndividual, resultsDir,validationFolder,resfile);
		//
	}
	/**
	 * Main
	 */
	public static void main(String[] args) throws IOException,Exception {
		//
		int totalNumberIterations=15000;
		//
		AlgorithmExecution algorithmExecution=new AlgorithmExecution();
		String mainResultsDir="C:\\Users\\zebastian\\Desktop\\Curso_Dirigido\\algo_results";
		String sourceDir="C:\\Users\\zebastian\\Desktop\\Curso_Dirigido\\aux_gen_files";
		File [] foldersArray=algorithmExecution.getSubFolderFromFolfer(sourceDir);
		//
		String sentTerm_Loc,sentLength_Loc,resulsDir;
		//
		/*
		 * Variable for results storing and traceability
		 */
		ByteBuffer outResBuf = ByteBuffer.allocateDirect(1*1024*1024);
		File controlFile;
		FileOutputStream fosControl;
		FileChannel channelControl;
		//
		int repeticions=0;
		while(repeticions<30){
			
			for(int folCount=0;folCount<foldersArray.length;folCount++){
			
				//For each dataSet
			
				sentTerm_Loc=foldersArray[folCount].getAbsolutePath()+"\\Sent-Term_freq.txt";
				sentLength_Loc=foldersArray[folCount].getAbsolutePath()+"\\sentencesLength.txt";
				//Create a new result directory every time it is executed
				resulsDir=mainResultsDir+"\\"+
						foldersArray[folCount].getName();
				if(!new File(resulsDir).exists()){
					new File(resulsDir).mkdir();
				}
				resulsDir=mainResultsDir+"\\"+
						foldersArray[folCount].getName()+"\\"+
					algorithmExecution.now("yyyy-MM-dd'_at_H='hh'_m='mm'_s='ss").trim();
				new File(resulsDir).mkdir();
				//End creation of result directories
				algorithmExecution.init(sentTerm_Loc, sentLength_Loc);
				/*
			 	* Algorithm Design
			 	*/
				controlFile=new File(resulsDir+"\\control.txt");
				fosControl = new FileOutputStream(controlFile);
				channelControl=fosControl.getChannel();
				algorithmExecution.algorithmDesign_I(resulsDir,totalNumberIterations,outResBuf,channelControl,mainResultsDir+"\\systems",foldersArray[folCount].getName()+"_"+repeticions);
				/*
				 * End Algorithm Design
				 */
			}
			repeticions++;
		}
	}
}
